#include "fs.h"

size_t ramdisk_read(void *buf, size_t offset, size_t len);
size_t ramdisk_write(const void *buf, size_t offset, size_t len);
size_t serial_write(const void *buf, size_t offset, size_t len);
size_t events_read(void *buf, size_t offset, size_t len);
size_t fb_write(const void *buf, size_t offset, size_t len);
size_t fbsync_write(const void *buf, size_t offset, size_t len);
size_t dispinfo_read(void *buf, size_t offset, size_t len);

typedef size_t (*ReadFn) (void *buf, size_t offset, size_t len);
typedef size_t (*WriteFn) (const void *buf, size_t offset, size_t len);

typedef struct {
  char *name;
  size_t size;
  size_t disk_offset;
  size_t open_offset;
  ReadFn read;
  WriteFn write;
} Finfo;

enum {FD_STDIN, FD_STDOUT, FD_STDERR, FD_FB};

size_t invalid_read(void *buf, size_t offset, size_t len) {
  panic("should not reach here");
  return 0;
}

size_t invalid_write(const void *buf, size_t offset, size_t len) {
  panic("should not reach here");
  return 0;
}

/* This is the information about all files in disk. */
static Finfo file_table[] __attribute__((used)) = {
  {"stdin", 0, 0, 0, invalid_read, invalid_write},
  {"stdout", 0, 0, 0, invalid_read, serial_write},
  {"stderr", 0, 0, 0, invalid_read, serial_write},
  {"/dev/events",0,0,0,events_read, invalid_write},
  {"/dev/fb",0,0,0,invalid_read,fb_write},
  {"/dev/fbsync",0,0,0,invalid_read,fbsync_write},
  {"/dev/tty", 0, 0, 0, invalid_read, serial_write},
  {"/proc/dispinfo",0,0,0,dispinfo_read,invalid_write},
#include "files.h"
};

#define NR_FILES (sizeof(file_table) / sizeof(file_table[0]))

void init_fs() {
  // TODO: initialize the size of /dev/fb
  size_t fds = NR_FILES;
  for (size_t i = 0; i < fds; i++) {
    if(!strcmp(file_table[i].name,"/dev/fb")){
      file_table[i].size = 12000000;//screen_width()*screen_height();
    }
  }
}

int fs_open(const char *pathname, int flags, int mode) {
  size_t fds = NR_FILES;
  printk("call fs open\n");

  for (size_t i = 0; i < fds; i++) {
    if(!strcmp(pathname,file_table[i].name)){
      file_table[i].open_offset = file_table[i].disk_offset;
      printk("path is %s tablePath is %s\n",pathname,file_table[i].name);
      return i;
    }
  }
  panic("File not found");
  return -1;
}

size_t fs_read(int fd, void *buf, size_t len) {

  Finfo rdop = file_table[fd];
  size_t read_len;

  if (fd >= NR_FILES) {
    printk("fd is greater than NR_FILES\n");
    return 1;
  }
  if (fd < 3) {
    printk("std stream can not be read\n");
    return 1;
  }
  if (rdop.read != NULL) {
    read_len = rdop.read(buf,file_table[fd].open_offset,len);
    file_table[fd].open_offset = file_table[fd].open_offset + read_len;
    return read_len;
  }
  // normal file operation
  size_t off = file_table[fd].open_offset + len;
  size_t maxlen = file_table[fd].disk_offset + file_table[fd].size;
  
  if(off > maxlen){
    len = file_table[fd].disk_offset + file_table[fd].size - file_table[fd].open_offset;
  }
  //for file size , it should be judge
  ramdisk_read(buf, file_table[fd].open_offset , len);
  //bug!!!!!!!!!!!!!!!!!!!!!!!!!!
  file_table[fd].open_offset = file_table[fd].open_offset + len;

  return len;
}

size_t fs_write(int fd, const void *buf, size_t len) {
  Finfo wdop = file_table[fd];
  size_t wlen;

  if (fd >= NR_FILES) {
    printk("fd is greater than NR_FILES\n");
    return 1;
  }

  if (wdop.write != NULL) {
    wlen = wdop.write(buf,file_table[fd].open_offset,len);
    file_table[fd].open_offset = file_table[fd].open_offset + wlen;
    return wlen;
  }
  // normal file operation
  size_t off = file_table[fd].open_offset + len;
  size_t maxlen = file_table[fd].disk_offset + file_table[fd].size;
  
  if(off > maxlen){
    // printk("the access len is too long\n");
    len = file_table[fd].disk_offset + file_table[fd].size - file_table[fd].open_offset;
    // return -1;
  }
  ramdisk_write(buf, file_table[fd].open_offset , len);
  file_table[fd].open_offset = file_table[fd].open_offset + len;
  return len;
}

size_t fs_lseek(int fd, size_t offset, int whence){
  
  // normal file operation
  if (whence == SEEK_END) {
    file_table[fd].open_offset = file_table[fd].disk_offset + file_table[fd].size;
  }
  if (whence == SEEK_CUR) {
    file_table[fd].open_offset = (file_table[fd].open_offset + offset) > (file_table[fd].disk_offset + file_table[fd].size) ? \
                                (file_table[fd].disk_offset + file_table[fd].size) : (file_table[fd].open_offset + offset);
  }

  if (whence == SEEK_SET) {
    // printk("size =%d offset=%d\n",file_table[fd].size,file_table[fd].open_offset);
    // if(!strcmp(file_table[fd].name,"/dev/fb")){
    //   file_table[fd].open_offset = offset;
    //   return offset;
    // }
    file_table[fd].open_offset = offset > file_table[fd].size ? \
                                (file_table[fd].disk_offset + file_table[fd].size) : (file_table[fd].disk_offset + offset);
  }
  return file_table[fd].open_offset - file_table[fd].disk_offset;
}

int fs_close(int fd) {
  return 0;
}
